#pragma once
//============================================================================
// Copyright (C) 2008-2013 Brett R. Jones 
// All Rights Reserved
//
// You may redistribute and/or modify for non commercial and commercial uses 
// provided this copyright notice remains in place and is not modified
//
// This code is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// brettjones1900@gmail.com
// http://www.p2panarchy.com
//============================================================================
#include "VxSktDefs.h"
#include "VxSktBuf.h"
#include "VxSktThrottle.h"
#include <VxUtilLib/VxRefCount.h>
#include <VxUtilLib/VxThread.h>
#include <VxUtilLib/VxSemaphore.h>
#include <VxUtilLib/VxMutex.h>
#include <VxUtilLib/CheezyCrypt.h>
#include <VxUtilLib/VxInetAddress.h>

//#pragma pack(4)

#ifdef WIN_PLATFORM
	#include <WinSock2.h>
	#include <ws2tcpip.h>
#else
	// LINUX
	#include <sys/types.h>
	#include <sys/socket.h>
	#include <arpa/inet.h>
#endif

enum EConnectionType
{
	eConnectionTypeDirect			= 0,
	eConnectionTypeRelayServer		= 1,	// connection as proxy server
	eConnectionTypeRelayClient		= 2,	// client side proxy connection
	eConnectionTypeProxyRequest		= 3,	// connection was requested by client

	eMaxConnectionType			// always last
};

// forward declare
class VxSktBase;
class VxSktBaseMgr;
class VxPktHdr;

class VxSktBase : public VxSktBuf, public VxSktThrottle
{
public:
	//=== vars ===//
	bool					m_bIsWebSkt;
	SOCKET					m_oSocket;				// handle to socket
	int						m_iSktId;				// socket unique id
	static int				m_iSktCnt;				// number of sockets created since program started

	VxInetAddrAndPort		m_oLclIp;				// local ip address
	std::string				m_strLclIp;				// local ip address in dotted form

	VxInetAddrAndPort		m_oRmtIp;				// remote (peer) ip address
	std::string				m_strRmtIp;				// remote (peer) ip address in dotted form

	//=== state vars ===//
    bool					m_bIsConnected;			// return true if is connected
	int						m_iConnectTimeout;		// how long to try to connect
	ESktType				m_eSktType;				// type of socket
	ESktCallbackReason		m_eSktCallbackReason;	// why callback is being performed
	RCODE					m_rcLastError;			// last error that occurred
    U32						m_u32RxLastActiveTime;	// last time received data
    U32						m_u32TxLastActiveTime;	// last time transmitted data

    U32						m_u32ToDeleteTime;

	VxThread				m_oSktRxThread;			// thread for handling socket receive
	VxThread				m_oSktTxThread;			// thread for handling socket transmit
	VxSemaphore				m_oSktTxSemaphore;		// semaphore for tx 
	VxMutex					m_oMutex;				// mutex
	VX_SKT_CALLBACK			m_pfnRecieve;			// receive function must be set by user
	VX_SKT_CALLBACK			m_pfnTransmit;			// optional function for transmit statistics
	bool					m_bClosingFromRxThread; // if true then call to close function was made by receive thread
	bool					m_bClosingFromDestructor; // if true then call to close function was made by destructor
	VxSktBaseMgr *			m_poSktMgr;
	int						m_iLastRxLen;			// size of last packet received
	int						m_iLastTxLen;			// size of last packet sent

	//=== app and user vars ===//
	void *					m_pvUserData;			// user defined data
	U64						m_u64AppData;

	//=== encryption vars ===//
	VxKey					m_oTxKey;				// encryption key for transmit
	VxKey					m_oRxKey;				// encryption key for receive
	VxCrypto				m_oTxCrypto;			// encryption object for transmit
	VxCrypto				m_oRxCrypto;			// encryption object for receive

	//=== proxy vars ===//
	EConnectionType			m_eConnectionType;
	VxSktBase *				m_poChainedSkt;
	VxSemaphore				m_oEventSemaphore;

    //=== vars ===//
    int						m_iRefCnt;
	std::string				m_strMulticastGroupIp;

    //============================================================================
    //=== constructor ===//


    virtual int incRefCnt()
    {
        m_iRefCnt++;
        return m_iRefCnt;
    }

    virtual int decRefCnt()
    {
        m_iRefCnt--;
        return m_iRefCnt;
    }

    virtual int getRefCnt()
    {
        return m_iRefCnt;
    }

	//=== constructor ===//
	VxSktBase();
	//=== destructor ===//
	virtual ~VxSktBase();

	bool					bindSocket( struct addrinfo * poResultAddr );
	bool					setReuseSocket( bool bReuse );
	bool					isIPv6Address (const char * addr );
	int						getIPv6ScopeID( const char * addr );
	const char *			stripIPv6ScopeID( const char * addr, std::string &buf );
	bool					joinMulticastGroup( VxInetAddress& oLclAddress, const char *mcastAddr );

	void					setTTL( U8 ttl );
	void					setAllowLoopback( bool allowLoopback );
	void					setAllowBroadcast( bool allowBroadcast );

	virtual	void			setConnectionType( EConnectionType eConnectionType )	{ m_eConnectionType = eConnectionType; }
	virtual	EConnectionType	getConnectionType( void )								{ return m_eConnectionType; }

	virtual	void			setIsWebSkt( bool bIsWebSkt )							{ m_bIsWebSkt = bIsWebSkt; }
	virtual	bool			isWebSkt( void )										{ return m_bIsWebSkt; }

	virtual	void			setChainedConnection( VxSktBase * poSkt )				{ m_poChainedSkt = poSkt; }
	virtual	VxSktBase *		getChainedConnection( void )							{ return m_poChainedSkt; }

	//! Set Receive Callback
	virtual void			SetRecieveCallback( VX_SKT_CALLBACK pfnRecieve );
	//! Set Transmit Callback ( optional for transmit statistics )
	virtual void			SetTransmitCallback( VX_SKT_CALLBACK pfnTransmit );

	//! get reason callback is being performed
	virtual ESktCallbackReason GetCallbackReason( void ){ return m_eSktCallbackReason; }
	//! set reason callback reason
	virtual void SetCallbackReason( ESktCallbackReason eReason ){ m_eSktCallbackReason = eReason; }

	//! Connect to 
	virtual RCODE ConnectTo(VxInetAddress&	oLclIp,	
							const char *	pIpOrUrl,						// remote ip or url
							U16				u16Port,						// port to connect to
							int				iTimeoutMilliSeconds = 10000 );	// milli seconds before connect attempt times out

	//! Do connect to from thread
	RCODE					DoConnectTo( void );

	//! send data without encrypting
	virtual RCODE			SendData(	const char *	pData,							// data to send
										int				iDataLen,						// length of data
										BOOL			bDisconnectAfterSend = false,	// if true disconnect after data is sent
										int				iSendTimeoutSeconds = 3);		// timeout attempt to send

	//! Close the socket
	virtual RCODE			CloseSkt( int iInstance = 0,  BOOL bFlushThenClose = true );

	//! return the socket type
	virtual ESktType		GetSktType( void );

	//! return true if is connected
	virtual BOOL			IsConnected( void );
	//! return true if is udp socket
	virtual BOOL			IsUdpSocket( void ) { return ((eSktTypeUdp == m_eSktType)||(eSktTypeUdpBroadcast == m_eSktType))?1:0; };
	//! return true if is tcp socket
	virtual BOOL			IsTcpSocket( void ) { return ((eSktTypeTcpConnect == m_eSktType)||(eSktTypeTcpAccept == m_eSktType))?1:0; };
	//! return true if is tcp accept socket
	virtual BOOL			IsAcceptSocket( void ){ return ((eSktTypeTcpAccept == m_eSktType)?1:0);};
	//! return true if is tcp connect socket
	virtual BOOL			IsConnectSocket( void ){ return ((eSktTypeTcpConnect == m_eSktType)?1:0);};

	//! get the unique id of this socket
	virtual int				GetSocketId( void ){ return m_iSktId;}			// return id
	//! get the sockets peer connection ip address as host order U32
	virtual RCODE			GetRemoteIp(	VxInetAddress &u32RetIp,		// return ip
											U16 &u16RetPort );	// return port
	//! get remote ip as string
	virtual const char *	GetRemoteIp( void );
	//! simpler version of GetRemoteIp returns ip as host order U32
	virtual RCODE			GetRemoteIp( VxInetAddress &u32RetIp );			// return ip
	//! get remote port connection is on
	virtual VxInetAddress	GetRemoteIpBinary( void ){ return m_oRmtIp;}			// return ip in host ordered binary u32

	//! get local ip as string
	virtual const char *	GetLocalIp( void );

	//! return a string describing the socket error
	//! most return strings are straight from operating system definitions
	//! but also returns error strings of errors defined in VxSktLib
	const char *			DescribeSktError(	RCODE rcSktError );

	virtual void			setUserData( void * pvData )	{ m_pvUserData = pvData; }
    virtual void *			getUserData( void )				{ return m_pvUserData; }
	virtual void			setAppDataU64( U64 u64AppData )	{ m_u64AppData = u64AppData; }
	virtual U64				getAppDataU64( void )			{ return m_u64AppData; }

	//! set socket to blocking or not
	virtual	RCODE			SetSktBlocking( BOOL bBlock );

	//=== encryption functions ===//
	//! return true if transmit encryption key is set
	virtual BOOL			IsTxEncryptionKeySet( void ) { return m_oTxKey.IsKeySet(); }
	//! return true if receive encryption key is set
	virtual BOOL			IsRxEncryptionKeySet( void ) { return m_oRxKey.IsKeySet(); }

	//! encrypt then send data using session crypto
	virtual RCODE			TxEncrypted(	const char *	pData, 					// data to send
											int				iDataLen,				// length of data
											BOOL			bDisconnect = false );	// if true disconnect after send
	//! encrypt with given key then send.. does not affect session crypto
	virtual RCODE			TxEncrypted(	VxKey *			poKey,					// key to encrypt with
											const char *	pData,					// data to send
											int				iDataLen,				// length of data
											BOOL			bDisconnect = false );	// if true disconnect after send
	//! encrypt then send packet using session crypto
	virtual RCODE			TxPacket(	VxPktHdr *		poPkt, 					// packet to send
										BOOL			bDisconnect = false );	// if true disconnect after send

	//! decrypt as much as possible in receive buffer
	virtual RCODE			DecryptRecieveData( void );

	//! used internally to get the remote ip address and set it in the vars
	RCODE					DoGetRmtAddress( void );

	//! fire up receive thread
	RCODE					StartRecieveThread( const char * pThreadName );

	void					SetRmtAddress( struct sockaddr_storage& oSktAddr );
	void					SetRmtAddress( struct sockaddr_in& oSktAddr );
	void					SetLclAddress( struct sockaddr_storage& oSktAddr );

private:
	bool					toSocketAddrInfo(	int sockType, 
												const char *addr, 
												int port, 
												struct addrinfo **addrInfo, 
												bool isBindAddr );
	bool					toSocketAddrIn(	const char *addr, 
											int port, 
											struct sockaddr_in *sockaddr, 
											bool isBindAddr );
};

